/*=====================================================================*
 *                   Copyright (C) 2011 Paul Mineiro                   *
 * All rights reserved.                                                *
 *                                                                     *
 * Redistribution and use in source and binary forms, with             *
 * or without modification, are permitted provided that the            *
 * following conditions are met:                                       *
 *                                                                     *
 *     * Redistributions of source code must retain the                *
 *     above copyright notice, this list of conditions and             *
 *     the following disclaimer.                                       *
 *                                                                     *
 *     * Redistributions in binary form must reproduce the             *
 *     above copyright notice, this list of conditions and             *
 *     the following disclaimer in the documentation and/or            *
 *     other materials provided with the distribution.                 *
 *                                                                     *
 *     * Neither the name of Paul Mineiro nor the names                *
 *     of other contributors may be used to endorse or promote         *
 *     products derived from this software without specific            *
 *     prior written permission.                                       *
 *                                                                     *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND              *
 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,         *
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES               *
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE             *
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER               *
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,                 *
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES            *
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE           *
 * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR                *
 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF          *
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT           *
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY              *
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE             *
 * POSSIBILITY OF SUCH DAMAGE.                                         *
 *                                                                     *
 * Contact: Paul Mineiro <paul@mineiro.com>                            *
 *=====================================================================*/

#ifndef __IWR_REPORT_WORKER_HH__
#define __IWR_REPORT_WORKER_HH__

#include <boost/format.hpp>
#include <boost/optional.hpp>
#include <ostream>
#include "example.hh"
#include "loss.hh"
#include "threadqueue.hh"

namespace flassol
{
  class IwrReportWorker
    {
      private:
        struct Stats
          {
            boost::optional<uint64_t>     bad_lineno;
            uint64_t                      badlines;
            uint64_t                      goodlines;
            float                         cur_predict;
            boost::optional<float>        cur_label;
            uint64_t                      cur_features;
            uint64_t                      example_count;
            double                        weighted_example_count;
            double                        loss;

            explicit
            Stats () : bad_lineno (boost::none),
                       badlines (0),
                       goodlines (0),
                       cur_predict (0),
                       cur_label (boost::none),
                       cur_features (0),
                       example_count (0),
                       weighted_example_count (0),
                       loss (0)
              {
              }

            Stats&
            operator+= (const Stats& s)
              {
                bad_lineno = s.bad_lineno;
                badlines += s.badlines;
                goodlines += s.goodlines;
                cur_predict = s.cur_predict;
                cur_label = s.cur_label;
                cur_features = s.cur_features;
                example_count += s.example_count;
                weighted_example_count += s.weighted_example_count;
                loss += s.loss;

                return *this;
              }

            void
            reset ()
              {
                bad_lineno = boost::none;
                badlines = 0;
                goodlines = 0;
                cur_predict = 0;
                cur_label = boost::none;
                cur_features = 0;
                example_count = 0;
                weighted_example_count = 0;
                loss = 0;
              }
          };

       static void
       do_report (std::ostream&                  report,
                  const Stats&                   since_last,
                  const Stats&                   cumulative)
         {
           report << boost::format ("%-9s %-9s %9llu %9.0f %9s %9f %8llu")
             % ((cumulative.weighted_example_count > 0)
                  ? (boost::format ("%-9.6f")
                       % (cumulative.loss / cumulative.weighted_example_count)
                    ).str ()
                  : "undefined"
               )
             % ((since_last.weighted_example_count > 0)
                  ? (boost::format ("%-9.6f")
                       % (since_last.loss / since_last.weighted_example_count)
                    ).str ()
                  : "undefined"
               )
             % cumulative.example_count
             % cumulative.weighted_example_count
             % (cumulative.cur_label
                   ? (boost::format ("%9f") % *cumulative.cur_label).str ()
                   : "unknown")
             % cumulative.cur_predict
             % cumulative.cur_features
           << std::endl;
         
           if (since_last.bad_lineno)
             {
               report << since_last.badlines
                      << " bad lines encountered since last, latest at offset " 
                      << *since_last.bad_lineno 
                      << std::endl;
             }
         }

        RegressionQueue& in;
        boost::optional<std::ostream&> report;
        boost::optional<std::ostream&> predict;
        uint64_t lineno;
        uint64_t output_number;
        Stats since_last;
        Stats cumulative;
        Loss& loss;

      public:
        IwrReportWorker (RegressionQueue&                _in,
                         boost::optional<std::ostream&>  _report,
                         boost::optional<std::ostream&>  _predict,
                         Loss&                           _loss)
          : in (_in),
            report (_report),
            predict (_predict),
            lineno (0),
            output_number (0),
            loss (_loss)
          {
          }

        void operator() ()
          {
            std::vector<boost::optional<RegressionExample> >* rvec = 0;

            if (report)
              {
                 *report <<
"cumul     since       example  weighted   current   current  current"
                     << std::endl <<
"loss      last        counter   counter     label   predict features"
                     << std::endl;
              }

            while (in.pop (rvec))
              {
                for (unsigned int n = 0; n < rvec->size (); ++n)
                  {
                    ++since_last.example_count;
                    ++lineno;

                    if (! (*rvec)[n])
                      {
                        since_last.bad_lineno = lineno;
                        ++since_last.badlines;
                      }
                    else
                      {
                        ++since_last.goodlines;
                        since_last.cur_predict = 0;
                        since_last.cur_label = (*rvec)[n]->label;
                        since_last.cur_features = 0;

                        for (unsigned int i = 0; i < (*rvec)[n]->partials->size (); ++i)
                          {
                            since_last.cur_predict += (*(*rvec)[n]->partials)[i][0].p;
                            since_last.cur_features += (*(*rvec)[n]->partials)[i][0].n;
                          }

                        if (predict)
                          {
                            predict->precision (5);
                            *predict << std::fixed << since_last.cur_predict;

                            if ((*rvec)[n]->tag)
                              {
                                *predict << "\t" << (*rvec)[n]->tag;
                              }

                            *predict << std::endl;
                          }

                        if ((*rvec)[n]->label)
                          {
                            since_last.loss +=
                              (*rvec)[n]->importance * 
                              loss.loss (since_last.cur_predict,
                                         *(*rvec)[n]->label);
                            since_last.weighted_example_count += 
                              (*rvec)[n]->importance;
                          }

                        if (since_last.example_count > (1ULL << output_number))
                          { 
                            cumulative += since_last;
              
                            if (report)
                              { 
                                do_report (*report, since_last, cumulative);
                              }
              
                            since_last.reset ();
              
                            ++output_number;
                          }

                        delete (*rvec)[n]->usecount;
                        delete (*rvec)[n]->estimationcount;
                        delete (*rvec)[n]->adaptivecount;
                      }
                  }

                delete rvec;
              }

            cumulative += since_last;

            if (report)
              {
                do_report (*report, since_last, cumulative);
                loss.report (*report, Loss::INFO);
              }
          }
    };
}

#endif // __IWR_REPORT_WORKER_HH__
